package com.smart.expand.config;

import com.smart.expand.bean.Constants;
import com.smart.expand.proxy.AnnotationProcessor;
import com.smart.expand.proxy.jdk.JDKDynamicProxy;
import com.smart.expand.annotation.AnnotationScan;
import com.smart.expand.bean.AnnotationMapping;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
public class BeanRegisterConfiguration implements ImportBeanDefinitionRegistrar {

    private static final Logger log = LoggerFactory.getLogger(BeanRegisterConfiguration.class);


    @SneakyThrows
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        List<String> list = new ArrayList<>();
        ClassPathScanningCandidateComponentProvider pathScanner = new ClassPathScanningCandidateComponentProvider(false);
        pathScanner.addIncludeFilter(new AnnotationTypeFilter(AnnotationScan.class));
        Set<BeanDefinition> pathComponents = pathScanner.findCandidateComponents("*");
        for (BeanDefinition component : pathComponents) {
            Class<?> clazz = Class.forName(component.getBeanClassName());
            AnnotationScan annotation = AnnotationUtils.findAnnotation(clazz, AnnotationScan.class);
            list.add(annotation.value());
        }
        for (String path : list) {
            ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
            scanner.addIncludeFilter(new AssignableTypeFilter(AnnotationProcessor.class));
            Set<BeanDefinition> components = scanner.findCandidateComponents(path);
            for (BeanDefinition component : components) {
                try {
                    Class<AnnotationProcessor<? extends Annotation>> clazz = (Class<AnnotationProcessor<? extends Annotation>>) Class.forName(component.getBeanClassName());
                    String name = clazz.getName();
                    name = name.substring(name.lastIndexOf('.') + 1);
                    name = name.substring(0, 1).toLowerCase(Locale.ROOT) + name.substring(1);
                    GenericBeanDefinition beanDefinition = (GenericBeanDefinition) BeanDefinitionBuilder
                            .genericBeanDefinition(clazz)
                            .getBeanDefinition();
                    beanDefinition.setBeanClass(clazz);
                    //走public构造器(且要求参数最多的且参数是在spring容器中)
                    beanDefinition.setAutowireMode(3);
                    registry.registerBeanDefinition(name, beanDefinition);
                    Constants.annotations.add(clazz);
                } catch (ClassNotFoundException e) {
                    log.error("== AnnotationPostProcessor -> {} ==", e.getMessage());
                }
            }

            ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
            provider.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
                ClassMetadata classMetadata = metadataReader.getClassMetadata();
                String className = classMetadata.getClassName();
                if (classMetadata.isAnnotation() || classMetadata.isFinal()) {
                    return false;
                }
                Class<?> aClass = null;
                try {
                    aClass = Class.forName(className);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                AnnotationMapping annotationMapping = new AnnotationMapping(aClass);
                for (Class<AnnotationProcessor<? extends Annotation>> processorClass : Constants.annotations) {
                    Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) ((ParameterizedType) processorClass.getGenericInterfaces()[0]).getActualTypeArguments()[0];
                    if (metadataReader.getAnnotationMetadata().hasAnnotation(annotationClass.getName()) ||
                            AnnotationUtils.findAnnotation(aClass, annotationClass) != null) {
                        annotationMapping.addClassAnnotations(processorClass);
                    }
                    Field[] declaredFields = aClass.getDeclaredFields();
                    for (Field declaredField : declaredFields) {
                        if (AnnotationUtils.findAnnotation(declaredField, annotationClass) != null) {
                            annotationMapping.addFieldAnnotations(declaredField, processorClass);
                        }
                    }
                    Method[] declaredMethods = aClass.getDeclaredMethods();
                    for (Method declaredMethod : declaredMethods) {
                        Parameter[] parameters = declaredMethod.getParameters();
                        for (Parameter parameter : parameters) {
                            if (AnnotationUtils.findAnnotation(parameter, annotationClass) != null) {
                                annotationMapping.addMethodAnnotations(declaredMethod, processorClass);
                            }
                        }
                        if (AnnotationUtils.findAnnotation(declaredMethod, annotationClass) != null) {
                            annotationMapping.addMethodAnnotations(declaredMethod, processorClass);
                        }
                    }
                }

                if (!annotationMapping.isEmpty()) {
                    Constants.annotationMappings.add(annotationMapping);
                }
                return false;
            });
            provider.findCandidateComponents(path);
            log.info("== annotationMappings -> {} ==", Constants.annotationMappings.stream().map(AnnotationMapping::getAClass).collect(Collectors.toList()));
            log.info("== annotations -> {} ==", Constants.annotations);

            for (AnnotationMapping annotationMapping : Constants.annotationMappings) {
                if (annotationMapping.isAutoWired()) {
                    Class aClass = annotationMapping.getAClass();
                    String name = aClass.getName();
                    name = name.substring(name.lastIndexOf('.') + 1);
                    name = name.substring(0, 1).toLowerCase(Locale.ROOT) + name.substring(1);
                    if (aClass.isInterface()) {
                        JDKDynamicProxy jdkDynamicProxy = JDKDynamicProxy.getInstance();
                        Object bean = jdkDynamicProxy.newProxyInstance(aClass);
                        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) BeanDefinitionBuilder
                                .genericBeanDefinition(bean.getClass())
                                .addConstructorArgValue(Proxy.getInvocationHandler(bean))
                                .getBeanDefinition();
                        beanDefinition.setBeanClass(bean.getClass());
                        //走public构造器(且要求参数最多的且参数是在spring容器中)
                        beanDefinition.setAutowireMode(3);
                        registry.registerBeanDefinition(name, beanDefinition);
                    } else {
                        Object bean = aClass.newInstance();
                        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) BeanDefinitionBuilder
                                .genericBeanDefinition(bean.getClass())
                                .getBeanDefinition();
                        beanDefinition.setBeanClass(bean.getClass());
                        //走public构造器(且要求参数最多的且参数是在spring容器中)
                        beanDefinition.setAutowireMode(3);
                        registry.registerBeanDefinition(name, beanDefinition);
                    }
                    annotationMapping.setBeanName(name);
                }
            }
        }

    }
}
